package it.polito.connectedcar.tcc.network;

import it.polito.connectedcar.general.network.NetworkConnectionManager;
import it.polito.connectedcar.message.OutgoingMessage;
import it.polito.connectedcar.message.TCCFromTrafficLightMessage;
import it.polito.connectedcar.message.TCCFromVehicleMessage;
import it.polito.connectedcar.util.Constants;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.concurrent.BlockingQueue;

/**
 * This implementation deals with the network management of a TCC. It receives
 * datagrams from the network , pack them into Messages the higher level can
 * understand and enqueue them in some queues used by the higher levels to
 * process such messages an different moment in time. It extracts Messages from
 * a queue of messages to be sent , filled by higher levels , and sequentially
 * unpack and send theme sequentially over the undelying network
 * 
 * @author Nicola Aresta
 * 
 */
public class TCCNetworkConnectionManager implements NetworkConnectionManager {

	private static final int SERVER_VEHICLES_LISTENING_PORT = Constants.TEST_TCC_FROM_VEHICLE_ADDRESS_PORT;
	private static final int SERVER_TRAFFICLIGHTS_LISTENING_PORT = Constants.TEST_TCC_FROM_TRAFFICLIGHT_ADDRESS_PORT;

	private Thread fromVehicleSocketReaderThread;
	private Thread toVehicleSocketWriterThread;
	private Thread fromTrafficLightThread;
	private DatagramSocket fromvehiclesSocket;
	private DatagramSocket fromTrafficLightsSocket;

	private BlockingQueue<TCCFromVehicleMessage> fromVehicleReadMessagesQueue;
	private BlockingQueue<OutgoingMessage> toWriteMessagesQueue;
	private BlockingQueue<TCCFromTrafficLightMessage> fromTrafficLightReadMessagesQueue;

	public TCCNetworkConnectionManager(
			BlockingQueue<TCCFromVehicleMessage> fromVehicleReadMessagesQueue,
			BlockingQueue<TCCFromTrafficLightMessage> fromTrafficLightReadMessagesQueue,
			BlockingQueue<OutgoingMessage> toWriteMessagesQueue)
			throws Exception {

		this.fromVehicleReadMessagesQueue = fromVehicleReadMessagesQueue;
		this.toWriteMessagesQueue = toWriteMessagesQueue;
		this.fromTrafficLightReadMessagesQueue = fromTrafficLightReadMessagesQueue;

		fromvehiclesSocket = new DatagramSocket(SERVER_VEHICLES_LISTENING_PORT);
		fromTrafficLightsSocket = new DatagramSocket(SERVER_TRAFFICLIGHTS_LISTENING_PORT);
		// socket.bind(null);
		fromVehicleSocketReaderThread = new Thread(new TCCFromVehicleSocketReader(fromvehiclesSocket));
		toVehicleSocketWriterThread = new Thread(new TCCToVehicleSocketWriter(fromvehiclesSocket));
		fromTrafficLightThread = new Thread(new TCCFromTrafficLightsSocketReader(fromTrafficLightsSocket));
		fromVehicleSocketReaderThread.start();
		toVehicleSocketWriterThread.start();
		fromTrafficLightThread.start();
	}

	/**
	 * Used to receive messages coming from the Socket opened just for receiving
	 * messages from vehicles.
	 * 
	 * @author Nicola Aresta
	 * 
	 */
	private class TCCFromVehicleSocketReader implements Runnable {
		private DatagramSocket dsocket;

		public TCCFromVehicleSocketReader(DatagramSocket socket) {
			this();
			this.dsocket = socket;
		}

		private TCCFromVehicleSocketReader() {

		}

		@Override
		public void run() {
			int datagramLimit = 1460;

			while (!dsocket.isClosed()) {
				try {
					DatagramPacket dp = new DatagramPacket(new byte[datagramLimit], datagramLimit);
					dsocket.receive(dp);
					ByteArrayOutputStream bas = new ByteArrayOutputStream();
					bas.write(dp.getData(), dp.getOffset(), dp.getLength());
					TCCFromVehicleMessage message = new TCCFromVehicleMessage(bas.toByteArray());
					bas.close();
					message.setSourceAddress((InetSocketAddress) dp.getSocketAddress());
					dp = null;
					fromVehicleReadMessagesQueue.offer(message);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Used to receive messages coming from the Socket opened just for receiving
	 * messages from traffic lights.
	 * 
	 * @author Nicola Aresta
	 * 
	 */
	private class TCCFromTrafficLightsSocketReader implements Runnable {

		private DatagramSocket dsocket;

		public TCCFromTrafficLightsSocketReader(DatagramSocket dsocket) {
			this();
			this.dsocket = dsocket;
		}

		private TCCFromTrafficLightsSocketReader() {

		}

		@Override
		public void run() {
			int datagramLimit = 1460;
			while (!dsocket.isClosed()) {
				try {
					DatagramPacket dp = new DatagramPacket(new byte[datagramLimit], datagramLimit);
					dsocket.receive(dp);
					ByteArrayOutputStream bas = new ByteArrayOutputStream();
					bas.write(dp.getData(), dp.getOffset(), dp.getLength());
					TCCFromTrafficLightMessage message = new TCCFromTrafficLightMessage(bas.toByteArray());
					if (fromTrafficLightReadMessagesQueue.offer(message)) {
						System.out.println("TCC traffic light message enqueued correctly");
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private class TCCToVehicleSocketWriter implements Runnable {

		private DatagramSocket dsocket;

		public TCCToVehicleSocketWriter(DatagramSocket socket) {
			this();
			this.dsocket = socket;
		}

		private TCCToVehicleSocketWriter() {

		}

		@Override
		public void run() {
			DatagramPacket dp = null;
			while (!dsocket.isClosed()) {
				try {

					OutgoingMessage m = toWriteMessagesQueue.take();
					byte[] bytes = m.getMessageAsBytes(false, null);
					for (InetSocketAddress add : m.getRecipientsAddresses()) {
						dp = new DatagramPacket(bytes, bytes.length, add);
						dsocket.send(dp);
					}

				} catch (InterruptedException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public void sendMessage(OutgoingMessage message) throws IOException {
		toWriteMessagesQueue.offer(message);
	}
}
